package org.robotfish.utils;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.ejml.simple.SimpleMatrix;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;

/**
 * 
 * @author Mart
 */
public class SVGDocument {
	
	Document dom;
	
	Element root;
	
	SimpleMatrix topRight;
	SimpleMatrix bottomLeft;
	double margin;
	
	double charWidth = 0.2;
	
	
	/**
	 * Should 'mm' be added at the end of each measure or not.
	 * Note, that all measures are *1000.
	 * Also note, that SVG viewbox attribute does not accept units.
	 */
	public boolean units = false;
	
	/**
	 * @see <a href="http://www.w3.org/TR/2008/REC-CSS2-20080411/syndata.html#value-def-color">Selection af distinguisiable colors</a>
	 * @see <a href="http://www.w3.org/TR/SVG/types.html#ColorKeywords">Recognized color keyword names</a>
	 */
	String[] colors = {"red", "green", "blue", "aqua", "maroon", "navy", "olive", "purple", "lightsalmon", "lightgray", "teal", "yellow"};
//	String[] colors = {"red", "green", "blue", "aqua", "fuchsia", "lime", "maroon", "navy", "olive", "purple", "silver", "teal", "yellow"};
	// 7ni jama
	
    static NumberFormat format = NumberFormat.getInstance(Locale.ENGLISH);
    /**
     * Maximum number of fraction digits is 3
     */
    static NumberFormat format3p = NumberFormat.getInstance(Locale.ENGLISH);
    static {
    	format.setGroupingUsed(false);
    	format3p.setGroupingUsed(false);
    	format3p.setMaximumFractionDigits(3);
    };
    
	/**
	 * The height of the text in user units (i.e., a value in the current user coordinate system)
	 * @see <a href="http://www.w3.org/TR/SVG/text.html#FontSizeProperty">font-size property of text</a>
	 */
	public double fontSize = 0.005;
	
	/**
	 * @see <a href="http://www.w3.org/TR/SVG/painting.html#StrokeWidthProperty">stroke-width property of curve</a>
	 */
	public double strokeWidth = 0.001;
	
	public double distance = 5;
	
	/**
	 * The ‘baseline-shift’ property allows repositioning of the dominant-baseline relative to the dominant-baseline of the parent text content element. 
	 * @see <a href="http://www.w3.org/TR/SVG/text.html#BaselineShiftProperty">baseline-shift property text</a>
	 */
	public String verticalAlignment = "sub";

	/**
	 * @see <a href="http://www.w3.org/TR/SVG/text.html#TextAnchorProperty">text-anchor property text</a>
	 */
	public String horizontalAlignment = "middle";


    /**
     * @param value
     * @param defaultValue value, in case string can not be parsed
     * @return kui sõne on number, siis see number, muidu defaultValue
     */
	public static double StringToDouble(String value, double defaultValue) {
	    double r = defaultValue;
	    try {
   		    Number number = format.parse(value);
   		    r = number.doubleValue();
	    }
	    catch (ParseException e){
	    }
	    return r;
	}

	/**
	 * Converts number to string using point
	 * @param number
	 * @return
	 */
	public static String DoubleToString(double number) {
		return 	format.format(number);
	}

	/**
	 * Converts number to string using point and max 3 fraction digits
	 * @param number
	 * @return
	 */
	public static String DoubleToString3p(double number) {
		return 	format3p.format(number);
	}

	/**
	 * get a measure with unit
	 * @param measure in m
	 * @return
	 */
	private String scaleMeasure(double measure) {
		if (units)
			return 	format.format(measure*1000)+"mm";
		return 	format.format(measure*1000);
	}

	private String scaleX(SimpleMatrix measure) {
		return scaleMeasure(measure.get(0));
	}

	private String scaleY(SimpleMatrix measure) {
		return 	scaleMeasure(-measure.get(1));
	}
	
	public Document getDOM() {
		return dom;
	}

	public Element getRoot() {
		return root;
	}
	
	public String getColour(int index) {
		return colors[index % colors.length];
	}

	public SVGDocument() {
		
        /////////////////////////////
        //Creating an empty XML Document

        //We need a Document
        DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder;
		try {
			docBuilder = dbfac.newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			throw new RuntimeException(e);
		}
		dom = docBuilder.newDocument();
		
		topRight = new SimpleMatrix(new double[][] {{Double.NEGATIVE_INFINITY}, {Double.NEGATIVE_INFINITY}, {0}});
		bottomLeft = new SimpleMatrix(new double[][] {{Double.POSITIVE_INFINITY}, {Double.POSITIVE_INFINITY}, {0}});
		margin = 0.1;
		
        ////////////////////////
        //Creating the XML tree

        //create the root element and add it to the document
        root = dom.createElement("svg");
        root.setAttribute("xmlns", "http://www.w3.org/2000/svg");
        root.setAttribute("version", "1.1");
        dom.appendChild(root);
        
		Comment comment = dom.createComment("NB! all coordinates are scaled (1000,-1000). Thereby unit is mm ");
		root.appendChild(comment);
        
	}
	
	public Comment createComment(String comment) {
		return dom.createComment(comment);
	}
	
	public Element createGroup() {
		return dom.createElement("g");
	}
	
/**	public Element createGroup(SimpleMatrix translation) {
		Element group = dom.createElement("g");
		group.setAttribute("transform","translate("+MeasureToString(translation.get(0))+","+MeasureToString(translation.get(1))+")");
		return group;
	}**/
	
	/**
	 * Expands topLeft and bottomRight to include p.
	 * @param p
	 */
	void expandFrame(SimpleMatrix p) {
		expandFrame( p.get(0), p.get(1) );
	}
	
	/**
	 * Expands topLeft and bottomRight to include (x,y).
	 * @param x
	 * @param y
	 */
	void expandFrame(double x, double y) {
		if (x > topRight.get(0)) {
			topRight.set(0, x);
		}
		if (x < bottomLeft.get(0)) {
			bottomLeft.set(0, x);
		}
		if (y > topRight.get(1)) {
			topRight.set(1, y);
		}
		if (y < bottomLeft.get(1)) {
			bottomLeft.set(1, y);
		}
	}
	
	public Element createText(SimpleMatrix p, String text, String color, double scale) {
		// TODO teksti langetamine fontSize*scale*0.35 võrra
		
		expandFrame( p.get(0)-text.length()*fontSize*0.5*charWidth*scale, p.get(1)+fontSize*0.8*scale );
		expandFrame( p.get(0)+text.length()*fontSize*0.5*charWidth*scale, p.get(1)-fontSize*0.2*scale );
		
        //create child element, add an attribute, and add to root
        Element element = dom.createElement("text");
        element.setAttribute("x", scaleX(p));
        element.setAttribute("y", scaleY(p));
        element.setAttribute("fill", color);
//        element.setAttribute("stroke", "none");
//        element.setAttribute("baseline-shift", verticalAlignment);
        element.setAttribute("text-anchor", horizontalAlignment);
        element.setAttribute("font-size", scaleMeasure(fontSize*scale));
        // see http://en.wikipedia.org/wiki/Serif
        element.setAttribute("font-family", "serif");
        Text content = dom.createTextNode(text);
        element.appendChild(content);		
		return element;
	}

	
	public Element createText(SimpleMatrix p, String text, String color) {
		return createText(p, text, color, 1);
	}
	
	/**
	 * Add text to the root element.
	 * @param p
	 * @param text
	 * @param color
	 */
	public void addText(SimpleMatrix p, String text, String color) {
		Element textElement = createText(p, text, color);
        root.appendChild(textElement);
	}
	
	/**
	 * 
	 * @param startRay
	 * @param endRay
	 * @param curvature
	 * @param color
	 * @return
	 * @see <a href="http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands">Path @ SVG specifivation</a>
	 */
	public Element createArc(SimpleMatrix startRay, SimpleMatrix endRay, double curvature, String color) {
		expandFrame( startRay );
		expandFrame( endRay );
		
        //create child element, add an attribute, and add to root
        Element element = dom.createElement("path");
        element.setAttribute("fill", "none");
        element.setAttribute("stroke", color);
        element.setAttribute("stroke-width", scaleMeasure(strokeWidth));
        StringBuilder d = new StringBuilder(100);
        // start point coordinates
        d.append('M'); // absolute coordinates
        d.append(scaleX(startRay));
        d.append(',');
        d.append(scaleY(startRay));
        // radius of curvature
        d.append('A'); // absolute coordinates
        String radius = scaleMeasure(Math.abs(1/curvature)); 
        d.append(radius);
        d.append(',');
        d.append(radius); // the arc can be elliptical but in our case it is not
        // rotation
        d.append(" 0 ");
        // "large-arc" is arc sweep of greater than or equal to 180 degrees
        char largeArcFlag = (Math.abs(endRay.get(2)-startRay.get(2))>=Math.PI)? '1' : '0';
        d.append(largeArcFlag);
        d.append(',');
        // sweep-flag '1' means tangential angle increases positively until the arc reaches end
        // NB! Because y is scaled by -1, the actual curvature is reversed
        char sweepFlag = (endRay.get(2)<startRay.get(2))? '1' : '0';
        d.append(sweepFlag);
        d.append(' ');
        // end point coordinates
        d.append(scaleX(endRay));
        d.append(',');
        d.append(scaleY(endRay));
        
        element.setAttribute("d", d.toString());
		return element;
	}

	
	/**
	 * Adds line element to the root
	 * @param start
	 * @param end
	 * @param color
	 */
	public void addArc(SimpleMatrix startRay, SimpleMatrix endRay, double curvature, String color) {
		Element arc = createArc(startRay, endRay, curvature, color);
        root.appendChild(arc);
	}
	
	
	
	//http://www.w3.org/TR/SVG/shapes.html#LineElement
	
	/**
	 * Loob joone objekti
	 * @param start
	 * @param end
	 * @param color
	 * @return
	 * @see <a href="http://www.w3.org/TR/SVG/shapes.html#LineElement">Line @ SVG specifivation</a>
	 */
	public Element createLine(SimpleMatrix start, SimpleMatrix end, String color) {
		expandFrame( start );
		expandFrame( end );
		
        //create child element, add an attribute, and add to root
        Element element = dom.createElement("line");
        element.setAttribute("stroke", color);
        element.setAttribute("stroke-width", scaleMeasure(strokeWidth));

        element.setAttribute("x1", scaleX(start));
        element.setAttribute("y1", scaleY(start));
        element.setAttribute("x2", scaleX(end));
        element.setAttribute("y2", scaleY(end));
		return element;
	}
	
	
	/**
	 * Adds line element to the root
	 * @param start
	 * @param end
	 * @param color
	 */
	public void addLine(SimpleMatrix start, SimpleMatrix end, String color) {
		Element line = createLine(start, end, color);
        root.appendChild(line);
	}
	
	private void setViewBoxAttribute() {
        SimpleMatrix dimensions = topRight.minus(bottomLeft);
        double marginAbs = Math.max(dimensions.get(0), dimensions.get(1)) * margin;
        
        /**
        root.setAttribute("width",scaleMeasure(dimensions.get(0)+2.0*marginAbs)+"mm");
        root.setAttribute("height",scaleMeasure(dimensions.get(1)+2.0*marginAbs)+"mm");
        addLine(topRight, bottomLeft, "gray");
        **/
        
        root.setAttribute("viewBox",
       		scaleMeasure(bottomLeft.get(0)-    marginAbs)+' '+scaleMeasure( -topRight.get(1)-    marginAbs)+' '+
       		scaleMeasure(dimensions.get(0)+2.0*marginAbs)+' '+scaleMeasure(dimensions.get(1)+2.0*marginAbs)
       		);
	}
	
	
	public void write(Writer sw) {
        //set up a transformer
        TransformerFactory transfac = TransformerFactory.newInstance();
        Transformer trans;
		try {
			trans = transfac.newTransformer();
	        trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
	        trans.setOutputProperty(OutputKeys.INDENT, "yes");
	
	        //create string from xml tree
	        StreamResult result = new StreamResult(sw);

	        if (!units)
	        	setViewBoxAttribute();
	        
	        DOMSource source = new DOMSource(dom);
	        trans.transform(source, result);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * Writes out a SVG file representing this transformation
	 */
	public String toString() {
        StringWriter sw = new StringWriter();
		
		/////////////////
		//Output the XML
		write(sw);
        return sw.toString();
	}
	
	/**
	 * Writes String <b>s</b> to file <b>filename</b>.
	 * @param filename
	 * @param s
	 */
	public static void stringToFile(String filename, String s) {
		try {
			PrintWriter out = new PrintWriter(filename);
			out.print(s);
			out.close();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		
	}

	/**
	 * @param filename
	 */
	public void writeToFile(String filename) {
		try {
			PrintWriter out = new PrintWriter(filename);
			write(out);
			out.close();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		
	}



}
